// Generator 函数是 es6 提供的一种异步编程解决方案。
// 在阮一峰 es6 文档中，提到了，可以把 Geneator 看成一个状态机，内部使用 yield 关键字封装了各种状态。初次之外，它还是一个遍历器对象生成函数。返回的遍历器对象，可以依次遍历 generator 函数内部的每一个状态.

const { reduce } = require("core-js/core/array");

function * firstGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

const hw = firstGenerator();

// yield 表达式，yield 可以用来暂停迭代操作，跟在 yield 后面的值，就是使用 next() 方法获取到对象的 value 属性 { value: 'value', done: false }。只要 generator 函数中有 yield 关键字的话，那么就能够使用 next() 一直获取 yield 后面值。当 yield 结束，且函数没有 return 的时候，就会返回 value 为 undefined { value: undefined, done: true }。

// next 方法的参数, yield 表达式本身是没有返回值的（或 undefined）。不过 next 方法可以通过携带参数，让上一个 yield 表达式拥有返回值（由于是上一个 yield 所以不能在初次调用 next 方法的时候携带参数，而且从语义上来讲第一个 next 方法是用来启动遍历器对象的，所以不用带有参数）

function * foo(x) {
  var y = 2 * (yield (x+1))
  var z = yield (y/3)
  return (x+y+z)
}

var a = foo(5)
a.next() // {value: 6, done: false}
a.next(12) // {value: 8, done: false}
a.next(13) // {value: 42, done: true}

// 如果想要第一次输入就获得值的话，可以在 generator 函数外面在包一层
function wrapper(generatorFunction) {
  return function (...args) {
    let generatorObj = generatorFunction(...args)
    generatorObj.next();
    return generatorObj;
  }
}

const wrapped = wrapper(function * () {
  console.log(`${yield}`)
  return 'DONE'
})

wrapped().next('hello!')

// 上面的函数，generator 函数使用 wrapper 和闭包包裹了一层。首先调用 wrapper 函数，并传入一个会返回 {value:'DONE', done: true} 的 generator 函数。来到 wrapper 内部后，wrapper 里的 generatorFunction 参数，就是前面传入的 generator 函数。然后 generatorFunction 启动，并赋值给 generatorObj ，下一步 generatorObj 调用 next() 方法 然后再返回 generatorObj wrapped(),最后外面再运行一次 next('hello') 方法。

// for...of 能够自动遍历 Generator 函数运行时生成的 Iterator 对象，这时候就不用再调用 next() 方法了

function * foo() {
  yield 1;
  yield 2;
  yield 3;
  yield 4;
  return 5;
}

for (let v of foo()) {
  console.log(v)
}

// Generator.prototype.throw() 用来抛出错误，并且抛出的错误能够被 catch 捕捉到。1. Generator.prototype.throw 和 window 的 throw 是不一样的。2. 如果 Generator 函数内部没有部署 try...catch 代码的话，那么 throw 方法抛出的错误则会被外部 try...catch 代码块捕捉，如果 Generator 外部和内部都没有部署 try...catch 代码块的话，那么程序就会报错，然后直接中断执行。

var g = function * () {
  while (true) {
    yield;
    console.log('内部捕获',e)
  }
}

var i = g();
i.next();

try {
  i.throw('a');
  i.throw('b'); // Generator 函数体外的 try 代码块捕获了一个 'a' 后，就不会继续执行了，所以没有 b
} catch (e) {
  console.log('外部捕获', e)
}

// throw 方法抛出的错误要被内部捕获，至少要执行过一次 next 方法才行。否则会报错

function * gen() {
  try {
    yield 1;
  } catch (e) {
    console.log('内部捕获');
  }
}

var g = gen();
g.throw(1); // Uncaught 1

// thorw 方法被捕获后，会附带执行下一条 yield 表达式，会顺便执行下一次 next() 

var gen = function * gen() {
  try {
    yield console.log('a')
  } catch (e) {

  }
  yield console.log('b');
  yield console.log('c')
}

var g = gen()
g.next()
g.throw()

// 上面代码中，g.throw 方法被捕获以后，自动执行了一次 next 方法，所以会打印b。另外，也可以看到，只要 Generator 内部部署了 try...catch 代码块的话，那么遍历器的 throw 方法抛出的错误，是不会影响下一次遍历的。而且 throw命令和 g.throw 命令是互不干扰的

// Generator.prototype.return() .这个方法用来终结遍历 Generator 函数，并且能够给定返回的值
// {value: 'return的值', done: true}

function * gen() {
  yield 1;
  yield 2;
  yield 3;
}

var g = gen();

g.next()
g.return('foo')
g.next()

// yield* 表达式是用来在一个 Generator 函数中调用另一个函数的时候使用的。

function * foo() {
  yield 'a';
  yield 'b'
} 

// 不适用 yield* 表达式，但是又想在一个 Generator 函数中使用另一个 Genenrator 函数的话，就需要对要使用的 Generator 函数使用 for 循环遍历
function * bar() {
  yield 'x';
  for (let i of foo()) {
    console.log(i)
  }
  yield 'y';
}

for (let v of bar()){
  console.log(v);
}

// 使用 yield* 表达式的话
function * bar() {
  yield 'x';
  yield* foo();
  yield 'y';
}

// 相当于
function * bar() {
  yield 'x';
  yield 'a';
  yield 'b';
  yield 'y';
}

// 相当于
function * bar() {
  yield 'x';
  for (let i of foo()) {
    yield i;
  }
  yield 'y'
}

for (let v of bar()) {
  console.log(v)
}

// 所以从之里也可以看出 yield* 是for...of 循环的一种简写，语法糖

function * inner() {
  yield 'hello'
}

function * outer1() {
  yield 'open'
  yield inner() // 使用 next() 方法来到这这里之后，就会返回一个 generator 对象
  yield 'close'
}

function * outer2() {
  yield 'open'
  yield * inner() // 这里使用了 yield* 表达式就会 返回一个 yield 'hello'
  yield * outer1() // 返回 'open' ,'hello','close'
  yield 'close'
}

var gen = outer2()

// 从语法上来说，如果 yield 表达式后面跟着的是一个遍历器对象，需要在 yield 表达式后面加上一个星号，表明它返回的是一个遍历器对象，这就是 yield* 表达式

function * gen() {
  yield* ['a','b','c']
}

gen().next()

// yield* 后面跟着一个数组，由于数组原生支持遍历器，因此就会遍历数组成员。除此之外，任何含有 Iterator 接口的数据结构，都能够被 yield 遍历

let read = (function *() {
  yield 'hello'
  yield* 'hello'
})()
read.next().value // 'hello'

let delegatedIterator = (function* () {
  yield 'Hello'
  yield 'Bye'
}())

let delegatingIterator = (function * (){
  yield 'Greetings'
  yield* delegatedIterator
  yield 'Ok, bye.'
});

for (let value of delegatingIterator()) {
  console.log(value)
}

// yield* 后面Generator 函数（没有 return 语句时），等同于在 Generator 函数内部部署了一个 for...of 循环

function * concat(iter1, iter2) {
  yield * iter1;
  yield * iter2;
}

// 相当于
function* concat(iter1, iter2) {
  for (var value of iter1) {
    yield value;
  }
  for (var value of iter2) {
    yield value;
  }
}

// 没有 return 语句时，yield* 是 for...of 的一种简写，是可以相互替代的。如果有 return 语句时，则需要是用 var value = yield* iterator 的形式获取 return 语句的值

// 如果 yield* 后面跟着一个数组，由于数组原生支持遍历器，所以 yield 遍历器就会遍历数组成员

function * gen() {
  yield* ['a','b','c']
}

gen().next() // { value: 'a', done: false }

// 上面代码中，如果 yield 命令后面没有星号的话，那么就会返回整个数组，加了星号返回的是数组的遍历器对象。除了数组外，任何数据结构只要有 iterator 接口，就可以被 yield* 遍历

let read = (function*() {
  yield 'hello'
  yield* 'hello'
})();

read.next().value; // hello 
read.next().value; // 依次调用 next(),会分别返回 'h','e','l','l','o'

// 上面代码中，yield 会返回整个字符串，yield* 会返回单个字符，这是因为 String 具有 iterator 接口，所以能够被 yield* 遍历

// 如果被代理的 Generator 函数有 return 语句的话，那么就可以像代理它的 Generator 函数返回数据

function* foo() {
  yield 2;
  yield 3;
  return 'foo';
}
function* bar() {
  yield 1;
  var v = yield* foo();
  console.log('v: '+ v);
  yield 4;
}
var it = bar();

it.next() // {value: 1,done: false}
it.next() // {value: 2,done: false}
it.next() // {value: 3,done: false}
it.next() // {value: undefined,done: true}
it.next() // v: foo // {value: 4,done: false}

// 上面代码在第四次调用 next 方法的时候，屏幕上会有输出，这是因为函数 foo 的 return 语句，向函数 bar 提供了返回值

function* genFunWithReturn() {
  yield 'a';
  yield 'b';
  return 'the result';
}
function* logReturnd(genObj) {
  let result = yield* genObj;
  console.log(result);
}

[...logReturnd(genFunWithReturn())] 
// 'the result'
// ['a', 'b']
// 上面代码中，存在着两次遍历。第一次遍历是扩展运算符遍历函数 logReturned 返回的遍历器对象，第二次是 yield* 语句遍历函数 genFunWithReturn 返回的遍历器对象。

// 我们可以很容易的使用 yield* 语句取出嵌套数组中的所有成员。
function* iterTree(tree) {
  if (Array.isArray(tree)) {
    for (let i = 0; i < tree.length ; i++) {
      debugger
      yield* iterTree(tree[i]);
    }
  } else {
    yield tree
  }
}
const tree = [ 'a', ['b', 'c'], ['d', 'e'] ];

for (let x of iterTree(tree)) {
  console.log(x);
}

// 由于扩展运算符默认调用 iterator 接口，所以上面这个函数也可以用于嵌套数组的平铺。
[...iterTree(tree)] // ['a','b','c','d','e']

// 作为对象属性的 Generator 函数
// 如果一个对象的属性是 Generator 函数，可以简写成下面的形状
let obj = {
  * myGeneratorMethod() {
    // 这个对象方法前面有个星号，表明它是一个 Generator 函数
  },
  // 也可以写成下面这样
  myGeneratorMethod: function* () {

  }
};

// Generator 函数的 this
// Generator 函数总是返回一个遍历器，ES6 规定这个遍历器是 Generator 函数的实例，所以它也就继承了 Generator 函数的 prototype 对象上的方法

function* g() {}

g.prototype.hello = function () {
  return 'hi';
};

let obj = g();

obj instanceof g // true
obj.hello() // 'hi'

// 上面代码使用了 instanceof 来证明函数 g 返回的遍历器 obj ，是 g 的实例，而且继承了 g.prototype。不过，也仅限于此，g 是不能够被当做普通构造函数的，它并不会生效，这是因为 g 总是返回遍历器对象，而不是 this 对象

function* g() {
  this.a = 11;
}

let obj = g();
obj.next();
obj.a // undfined 因为返回的是遍历器对象，不是 this 对象，所以无法取得 this.a 的值
// 所以 Generator 函数实际上并不能被当做构造函数，自然也就无法使用 new 操作符，否则就会报错。但是我们可以通过生成一个空对象，使用 call 方法绑定 Generator 函数内部的 this 。这样，构造函数调用以后，这个空对象就是 Generator 函数的实例对象了。

function* F() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
const f = F.call(F.prototype);

f.next() // {value: 2, done: false}
f.next() // {value: 3, done: false}
f.next() // {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3

// 然后将 F 改成构造函数，就可以对它执行 new 命令了
function* gen() {
  this.a = 1;
  yield this.b = 2;
  yield this.c = 3;
}
function F() {
  return gen.call(gen.prototype)
}
var f = new F();

f.next() // {value:2,done:false};
f.next() // {value:3,done:false};
f.next() // {value:undefined,done:true};

// 状态机：Generator 是实现状态机的最佳结构
const ticking = true;
const clock = function() {
  ticking ? console.log('tick') : console.log('tock');
  ticking = !ticking;
}
// 上面这个状态机有 tick 和 tock 两种状态，每运行一次，就改变一次状态。这个函数如果用 Generator 实现，就是下面这样
const clock = function() {
  while(true) {
    console.log('tick');
    yield;
    console.log('tock')
    yield;
  }
};

// 协程,一种程序运行的方式，可以理解成“协作的线程”或“协作的函数”。写成既可以用单线程实现，也可以用多线程实现。前者是一种特殊的资子例程，后者是一种特殊的线程。

// generator 的应用
// 1. 异步操作的同步化表达，因为 Generator 函数的暂停执行的效果。我们可以将异步操作写在 yield 表达式里面，等到调用 next() 方法的时候再往后执行。这实际上就等同于不用写 callback 了，因为异步操作的后续操作可以放在 yield 表达式下面，这些操作只会在调用 next 方法的时候才执行。（处理异步操作，改写回调函数）

function* loadUI() {
  showLoadingScreen();
  yield loadUIDataAsynchronously();
  hideLoadingScreen();
}
var loader = loadUI()
// 加载UI
loader.next()

// 卸载UI
loader.next()